package org.dromara.vehicle.utils;

import org.dromara.common.core.utils.SpringUtils;
import org.dromara.vehicle.modules.configDistrict.service.GeoConfigDistrictService;
import org.dromara.vehicle.modules.geoFence.domain.GeoFence;

import java.util.ArrayList;
import java.util.List;

/**
 * 地理围栏工具类
 */
public class GeoFenceUtils {

    /**
     * 判断点是否在围栏内
     *
     * @param point 待检测的点 [经度, 纬度]
     * @param fence 围栏对象
     * @return 点是否在围栏内
     */
    public static boolean isPointInFence(double[] point, GeoFence fence) {
        return switch (fence.getType()) {
            case 1 -> {
                double[] center = parseCoordinate(fence.getCenter());
                yield isPointInCircle(point, center, fence.getRadius());
            }
            case 2 -> {
                List<double[]> polygonPoints = parseCoordinateList(fence.getPoints());
                yield isPointInPolygon(point, polygonPoints);
            }
            case 3 -> {
                List<double[]> linePoints = parseCoordinateList(fence.getPoints());
                yield isPointNearPolyline(point, linePoints, Double.parseDouble(fence.getBufferradius()));
            }
            case 4 -> {
                // 需要调用外部API获取行政区划边界
                List<List<double[]>> districtBoundaries = getDistrictBoundaries(fence.getAdcode());
                yield isPointInDistrict(point, districtBoundaries);
            }
            default -> false;
        };
    }

    /**
     * 判断点是否在圆内
     */
    public static boolean isPointInCircle(double[] point, double[] center, double radius) {
        double lng1 = point[0], lat1 = point[1];
        double lng2 = center[0], lat2 = center[1];

        // 计算两点之间的距离（米）
        double distance = calculateDistance(lng1, lat1, lng2, lat2);
        return distance <= radius;
    }

    /**
     * 判断点是否在多边形内
     */
    public static boolean isPointInPolygon(double[] point, List<double[]> polygon) {
        int intersections = 0;
        double lng = point[0], lat = point[1];
        int count = polygon.size();

        for (int i = 0; i < count; i++) {
            double[] point1 = polygon.get(i);
            double[] point2 = polygon.get((i + 1) % count);

            double lng1 = point1[0], lat1 = point1[1];
            double lng2 = point2[0], lat2 = point2[1];

            // 判断射线是否与边相交
            if ((lat1 > lat) != (lat2 > lat) &&
                (lng < (lng2 - lng1) * (lat - lat1) / (lat2 - lat1) + lng1)) {
                intersections++;
            }
        }

        // 交点为奇数，则点在多边形内部
        return (intersections & 1) == 1;
    }

    /**
     * 判断点是否在线缓冲区内
     */
    public static boolean isPointNearPolyline(double[] point, List<double[]> polyline, double bufferRadius) {
        double minDistance = Double.MAX_VALUE;

        // 计算点到线段的最短距离
        for (int i = 0; i < polyline.size() - 1; i++) {
            double[] point1 = polyline.get(i);
            double[] point2 = polyline.get(i + 1);

            double distance = distanceToSegment(point, point1, point2);
            minDistance = Math.min(minDistance, distance);

            if (minDistance <= bufferRadius) {
                return true; // 优化：找到一个小于缓冲区的距离就返回
            }
        }

        return minDistance <= bufferRadius;
    }

    /**
     * 判断点是否在行政区划内
     */
    public static boolean isPointInDistrict(double[] point, List<List<double[]>> boundaries) {
        // 行政区划可能由多个多边形组成（比如包含离岛）
        for (List<double[]> polygon : boundaries) {
            if (isPointInPolygon(point, polygon)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 计算两点之间的距离（米）
     */
    public static double calculateDistance(double lng1, double lat1, double lng2, double lat2) {
        final double EARTH_RADIUS = 6371000; // 地球半径，单位：米

        double radLat1 = Math.toRadians(lat1);
        double radLat2 = Math.toRadians(lat2);
        double deltaLat = Math.toRadians(lat2 - lat1);
        double deltaLng = Math.toRadians(lng2 - lng1);

        double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
            Math.cos(radLat1) * Math.cos(radLat2) *
                Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return EARTH_RADIUS * c;
    }

    /**
     * 计算点到线段的距离
     */
    public static double distanceToSegment(double[] point, double[] segmentStart, double[] segmentEnd) {
        double lng = point[0], lat = point[1];
        double lng1 = segmentStart[0], lat1 = segmentStart[1];
        double lng2 = segmentEnd[0], lat2 = segmentEnd[1];

        double A = lng - lng1;
        double B = lat - lat1;
        double C = lng2 - lng1;
        double D = lat2 - lat1;

        double dot = A * C + B * D;
        double lenSq = C * C + D * D;
        double param = dot / lenSq;

        double xx, yy;

        if (param < 0 || (lng1 == lng2 && lat1 == lat2)) {
            xx = lng1;
            yy = lat1;
        } else if (param > 1) {
            xx = lng2;
            yy = lat2;
        } else {
            xx = lng1 + param * C;
            yy = lat1 + param * D;
        }

        return calculateDistance(lng, lat, xx, yy);
    }

    /**
     * 解析单个坐标
     */
    private static double[] parseCoordinate(String coordinate) {
        String[] parts = coordinate.split(",");
        return new double[]{Double.parseDouble(parts[0]), Double.parseDouble(parts[1])};
    }

    /**
     * 解析坐标列表
     */
    private static List<double[]> parseCoordinateList(String coordinates) {
        List<double[]> result = new ArrayList<>();
        String[] points = coordinates.split(";");

        for (String point : points) {
            result.add(parseCoordinate(point));
        }

        return result;
    }

    /**
     * 获取行政区划边界
     */
    private static List<List<double[]>> getDistrictBoundaries(String adCode) {
        return SpringUtils.getBean(GeoConfigDistrictService.class).getDistrictBoundaries(adCode);
    }

}
